#pragma once
#include "CConvert.h"
#include <fstream>
#include <iostream>
#include <windows.h>
#include <shellapi.h>
#include <shlwapi.h>
#include "utf8.h"
#include "FilesRefactor.h"
#include "TextFileWriter.h"
#include "ANSITextFileWriter.h"

class FilesRefactorUtils
{
private:
	static bool unique_list_comparator(const wstring& wstr1, const wstring& wstr2)
	{
		size_t pos = wstr1.find_last_of(L"." + wstring(SPLIT_EXTENSION));
		wstring ext1(L"");
		int num1 = -1;
		if (pos != wstring::npos)
		{
			ext1 = wstr1.substr(pos+1, wstr1.length());
			wcout << ext1 << endl;
			num1 = _wtoi(ext1.c_str());
		}

		pos = wstr2.find_last_of(L"." + wstring(SPLIT_EXTENSION));
		wstring ext2(L"");
		int num2 = -2;
		if (pos != wstring::npos)
		{
			ext2 = wstr2.substr(pos+1, wstr2.length());
			wcout << ext2 << endl;
			num2 = _wtoi(ext2.c_str());
		}

		return num1==num2;
	}

	static bool sort_list_comparator(const wstring& wstr1, const wstring& wstr2)
	{
		size_t pos = wstr1.find_last_of(L"." + wstring(SPLIT_EXTENSION));
		wstring ext1(L"");
		int num1 = 0;
		if (pos != wstring::npos)
		{
			ext1 = wstr1.substr(pos+1, wstr1.length());
			num1 = _wtoi(ext1.c_str());
		}
		else
		{
			return true;
		}

		pos = wstr2.find_last_of(L"." + wstring(SPLIT_EXTENSION));
		wstring ext2(L"");
		int num2 = 0;
		if (pos != wstring::npos)
		{
			ext2 = wstr2.substr(pos+1, wstr2.length());
			num2 = _wtoi(ext2.c_str());
		}
		else
		{
			return false;
		}

		return num1 <= num2;
	}
	
public:

	static int (*s_FrogMessageBoxFunc)(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType);
	static int FrogMessageBox(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType);

	static void convertWstringToString(const wstring& source, string& dest)
	{
		//wstring tmp(source);
		wchar_t orig[512];
		wcscpy_s(orig, 512, source.c_str());

		// Convert to a char*
		size_t origsize = wcslen(orig) + 1;
		size_t convertedChars = 0;
		char nstring[512];
		wcstombs_s(&convertedChars, nstring, origsize, orig, _TRUNCATE);
		
		dest = string(nstring);
	}

	static void getTotalsForFolder(const wstring& folder, int* totalFiles, INT64* sumSizeFiles, int* totalFolders, int* sumSizeFolders, int* totalSubFolders)
	{
		list<wstring> innerList;
		MakeListInDir(folder, innerList);

		for(list<wstring>::const_iterator iter=innerList.begin(); iter!=innerList.end(); iter++)
		{
			wstring fileName(*iter);

			WIN32_FIND_DATA FileData;
			HANDLE hFind;

			hFind =	FindFirstFile(fileName.c_str(), &FileData);
			FindClose(hFind);

			if (hFind == INVALID_HANDLE_VALUE) 
			{
				wcout << L"FindFirstFile failed for ";
				PrintUTF16ToStream(fileName, true, wcout);
				continue;
			}
			
			INT64 size = GetFileSize(fileName);

			if (FileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
			{
				(*totalFolders)++;
				(*sumSizeFolders) += size;
				(*totalSubFolders)++;

				getTotalsForFolder(fileName, totalFiles, sumSizeFiles, totalFolders, sumSizeFolders, totalSubFolders);
			}
			else
			{
				(*totalFiles)++;
				(*sumSizeFiles) += size;
			}
		}
	}

	static void WriteAttrsOfFileHTML(const wstring& fileName, TextFileWriter& filewriter)
	{
		WIN32_FIND_DATA FileData;
		HANDLE hFind;

		hFind =	FindFirstFile(fileName.c_str(), &FileData);

		if (hFind == INVALID_HANDLE_VALUE) 
		{
			wcout << L"FindFirstFile failed for ";
			FilesRefactorUtils::PrintUTF16ToStream(fileName, true, wcout);
			return;
		}

		FILETIME ft = FileData.ftCreationTime;
		wstring creationTime = CConvert::ToWString(&ft);

		ft = FileData.ftLastAccessTime;
		wstring lastAccessTime = CConvert::ToWString(&ft);

		FindClose(hFind);

		INT64 fileSize = GetFileSize(fileName);

		wstring isFile;
		if (FileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
		{
			isFile = L"DIR";
		}
		else
		{
			isFile = L"FILE";
		}

		filewriter.writeLine(L"\t\t\t<td style=\"padding-right: 20px\">" + creationTime + L"</td>");
		filewriter.writeLine(L"\t\t\t<td style=\"padding-right: 20px\">" + lastAccessTime + L"</td>");
		wchar_t fsize[50];
		swprintf(fsize, L"%I64d", fileSize);
		filewriter.writeLine(L"\t\t\t<td style=\"padding-right: 20px\">" + wstring(fsize) + L"</td>");
		filewriter.writeLine(L"\t\t\t<td style=\"padding-right: 20px\">" + isFile + L"</td>");

	}

	static void WriteAttrsOfFile(const wstring& fileName, TextFileWriter &filewriter)
	{
		WIN32_FIND_DATA FileData;
		HANDLE hFind;

		hFind =	FindFirstFile(fileName.c_str(), &FileData);

		if (hFind == INVALID_HANDLE_VALUE) 
		{
			wcout << L"FindFirstFile failed for ";
			FilesRefactorUtils::PrintUTF16ToStream(fileName, true, wcout);
			return;
		}

		FILETIME ft = FileData.ftCreationTime;
		wstring creationTime = CConvert::ToWString(&ft);

		ft = FileData.ftLastAccessTime;
		wstring lastAccessTime = CConvert::ToWString(&ft);

		FindClose(hFind);

		INT64 fileSize = GetFileSize(fileName);

		wstring isFile;
		if (FileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
		{
			isFile = L"<DIR>";
		}
		else
		{
			isFile = L"<FILE>";
		}

		filewriter.write(L"\t");
		filewriter.write(creationTime.c_str());
		filewriter.write(L"\t");
		filewriter.write(lastAccessTime.c_str());
		filewriter.write(L"\t");
		wchar_t fsize[50];
		wsprintf(fsize, L"%I64d", fileSize);
		filewriter.write(fsize);
		filewriter.write(L"\t");
		filewriter.write(isFile.c_str());

		//filest << L"\t";
		//filest << creationTime.c_str();
		//filest << L"\t";
		//filest << lastAccessTime.c_str();
		//filest << L"\t";
		//filest << fileSize;
		//filest << L"\t";
		//filest << isFile.c_str();
	}

	static INT64 GetFileSize(const wstring& fileName)
	{
		WIN32_FIND_DATA FileData;
		HANDLE hFind;

		hFind =	FindFirstFile(fileName.c_str(), &FileData);
		FindClose(hFind);

		if (hFind == INVALID_HANDLE_VALUE) 
		{
			wcout << L"FindFirstFile failed for ";
			PrintUTF16ToStream(fileName, true, wcout);
		}

		if (FileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
		{
			wstring tmp(fileName + L"\\*.*");
			hFind =	FindFirstFile(tmp.c_str(), &FileData);
			INT64 numFiles = 0;

			while (FindNextFile(hFind, &FileData))
			{
				if( StrCmpW(FileData.cFileName, L".") != 0 && StrCmpW(FileData.cFileName,L"..") != 0 )
				{
				   numFiles ++;
				}
			}
			
			FindClose(hFind);

			return numFiles;
		}
		else
		{
			return ((INT64)FileData.nFileSizeHigh << 32) + FileData.nFileSizeLow;
		}
	}

	static void PrintUTF16ToStream(const wstring& str, bool withLineFeed, wostream &stream)
	{
		// Convert UTF16 to UTF8
		wstring utf8line;
		utf8::utf16to8(str.begin(), str.end(), 
                       back_inserter(utf8line));

		stream << utf8line;

		if (withLineFeed)
		{
			stream << endl;
		}
	}

	static void GetFilePathNoExt(wstring& retName, const wstring& path)
	{
		wstring tmp = L"";
		size_t pos = path.find_last_of(L".");
		if (pos != wstring::npos)
		{
			tmp = path.substr(pos+1, path.length());
			
			size_t postmp = tmp.find_last_of(L"\\");
			if (postmp != wstring::npos)
			{
				retName  = path;
			}
			else
			{
				retName  = path.substr(0, pos);
			}
		}		
	}

	static void CheckSortListFilesForJoin(FRRESPONSE& res, list<wstring>& srcList)
	{
		// Check files' names and extensions:
		//	1. Checking the file name (all file path without the extension: "c:\123\test.bmp" is "c:\123\test"
		//	2. If the first test passes than checking the full path for duplications. Duplicate paths aren't allowed.
		wstring fName;
		GetFilePathNoExt(fName, srcList.front());
		for(list<wstring>::const_iterator iter=srcList.begin(); iter != srcList.end(); iter++)
		{
			wstring tmpFName;
			GetFilePathNoExt(tmpFName, *iter);
			if (tmpFName != fName)
			{
				wcout << L"CheckSortListFilesForJoin: wrong files selection for Join. FAIL_SOME_FILES_WRONG_KIND" << endl;
				
				// not all files' names are equal... not good
				FilesRefactorUtils::FillResponse(res, FAIL_SOME_FILES_WRONG_KIND, JOIN, L"Wrong files selection for Join.", 
					0, srcList.size());
				return;
			}
			else
			{
				// the files' names are equal... now check the full path
				list<wstring>::const_iterator iter1 = iter;
				iter1++;
				for(; iter1 != srcList.end(); iter1++)
				{
					if (*iter1 == *iter)
					{
						wcout << L"CheckSortListFilesForJoin: wrong files selection for Join. FAIL_PATH_DUPLICATION" << endl;
						
						// path duplication found... not good
						FilesRefactorUtils::FillResponse(res, FAIL_PATH_DUPLICATION, JOIN, L"Wrong files selection for Join.", 
							0, srcList.size());
						return;
					}
				}
			}
		}

		// sorting the list
		srcList.sort(sort_list_comparator);
		
		// check if list size is zero?!
		if (srcList.size() < 2)
		{
			wcout << L"CheckSortListFilesForJoin: wrong files selection for Join. FAIL_WRONG_SIZE_OF_LIST" << endl;
			
			FilesRefactorUtils::FillResponse(res, FAIL_WRONG_SIZE_OF_LIST, JOIN, L"Wrong number of files in selection for Join.", 
				0, srcList.size());
			return;
		}

		// checking first value (needs to be "blabla.ffs0"
		wstring member = srcList.front();
		size_t pos = member.find_last_of(L"." + wstring(SPLIT_EXTENSION));
		wstring ext(L"");
		if (pos != wstring::npos)
		{
			ext = member.substr(pos+1, member.length());
			if (_wtoi(ext.c_str()) != 0)
			{
				wcout << L"CheckSortListFilesForJoin: wrong files selection for Join. FAIL_WRONG_SIZE_OF_LIST" << endl;
				
				FilesRefactorUtils::FillResponse(res, FAIL_WRONG_FILE_SELECTION, JOIN, wstring(L"Missing file with ." + wstring(SPLIT_EXTENSION) + L"0 in selection for Join.").c_str(), 
					0, srcList.size());
				return;
			}
		}
		else
		{
			wcout << L"CheckSortListFilesForJoin: wrong files selection for Join. FAIL_WRONG_SIZE_OF_LIST" << endl;
			
			FilesRefactorUtils::FillResponse(res, FAIL_WRONG_FILE_SELECTION, JOIN, wstring(L"Missing file with ." + wstring(SPLIT_EXTENSION) + L"0 in selection for Join.").c_str(), 
				0, srcList.size());
			return;
		}

		// checking last value (needs to be "blabla.ffs[size-1]"
		int size=srcList.size();
		member = srcList.back();
		pos = member.find_last_of(L"." + wstring(SPLIT_EXTENSION));
		if (pos != wstring::npos)
		{
			ext = member.substr(pos+1, member.length());
			if (_wtoi(ext.c_str()) != size-1)
			{
				wcout << L"CheckSortListFilesForJoin: wrong files selection for Join. FAIL_WRONG_SIZE_OF_LIST" << endl;
				
				FilesRefactorUtils::FillResponse(res, FAIL_WRONG_FILE_SELECTION, JOIN, L"Missing files with in selection for Join.", 
					0, srcList.size());
				return;
			}
		}
		else
		{
			wcout << L"CheckSortListFilesForJoin: wrong files selection for Join. FAIL_WRONG_SIZE_OF_LIST" << endl;
			
			FilesRefactorUtils::FillResponse(res, FAIL_WRONG_FILE_SELECTION, JOIN, L"Missing files with in selection for Join.", 
				0, srcList.size());
			return;
		}

		// everything is ok!
		FilesRefactorUtils::FillResponse(res, SUCCESS, JOIN, L"", srcList.size(), 0);
	}

	static void GetBitmapInfoHeader( LPCWSTR filename, BITMAPINFOHEADER *bitmapInfoHeader )
	{
		FILE *filePtr ;
		WORD BITMAP_ID = 0x4D42 ;
	   
		_wfopen_s(&filePtr, filename, L"rb" ) ;
		if ( filePtr == NULL )
		   return ;
	   
		BITMAPFILEHEADER bitmapFileHeader ;
		fread( &bitmapFileHeader, sizeof(BITMAPFILEHEADER), 1, filePtr ) ;
	   
		if ( bitmapFileHeader.bfType != BITMAP_ID )
		{
			 fclose( filePtr ) ;
			 printf( "Not a Bitmap." ) ;
			 return ;
		}
	   
		fread( bitmapInfoHeader, sizeof(BITMAPINFOHEADER), 1, filePtr ) ;
	   
		fclose( filePtr ) ;
	}

	//used to convert paths
	static void Char2Wchar(wchar_t* pDest, char* pSrc)
	{
		size_t origsize = strlen(pSrc) + 1;
		size_t convertedChars = 0;
		mbstowcs_s(&convertedChars, pDest, origsize, pSrc, _TRUNCATE);
	}
	
	static bool GetTempFile(const wstring& folder, wstring& retVal)
	{
		WCHAR szTempName[MAX_PATH];
		UINT dwTempFile = GetTempFileName(folder.c_str(), // folder to create in
								  L"frgtmp",  // temp file name prefix 
								  0,            // create unique name 
								  szTempName);  // buffer for name 
		
		if (dwTempFile == 0)
		{
			wcout << "GetTempFileName failed!" << endl;
			return false;
		}

		retVal = szTempName;
		return true;
	}

	static void MakeListInDir(const wstring& dir, list<wstring>& retVal)
	{
		WIN32_FIND_DATA ffd;
		HANDLE hFind = INVALID_HANDLE_VALUE;
		DWORD dwError=0;

		wstring findIn(dir + L"\\*");
		hFind = FindFirstFile(findIn.c_str(), &ffd);

		if (INVALID_HANDLE_VALUE == hFind) 
		{
			wcout << L"MakeListInDir: Error trying FindFirstFile" << endl;
			return;
		}
		
		FindNextFile(hFind, &ffd);
		while (FindNextFile(hFind, &ffd) != 0)
		{
			wstring tmp(dir + L"\\" + ffd.cFileName);
			retVal.push_back(tmp);
		}

		dwError = GetLastError();
		if (dwError != ERROR_NO_MORE_FILES) 
		{
			wcout << L"Unexpected Error." << endl;
			return;
		}

		FindClose(hFind);
	}

	static int CalcTotalFiles(HWND parent, const std::list<wstring> &filesList, int count)
	{
		for(list<wstring>::const_iterator iter = filesList.begin(); iter != filesList.end(); iter++)
		{
			if (!PathFileExists(iter->c_str()))
			{
				wcout << L"CalcTotalFiles: File doesn't exist: ";
				FilesRefactorUtils::PrintUTF16ToStream(*iter, true, wcout);
				continue;
			}

			FileMachine fm(parent, *iter);
			if (fm.IsFile())
			{
				count++;
				continue;
			}

			WIN32_FIND_DATA ffd;
			HANDLE hFind = INVALID_HANDLE_VALUE;
			DWORD dwError=0;

			wstring findIn(*iter + L"\\*");
			hFind = FindFirstFile(findIn.c_str(), &ffd);

			if (INVALID_HANDLE_VALUE == hFind) 
			{
				wcout << L"calcTotalFiles: Error trying FindFirstFile" << endl;
				return -1;
			} 

			while (FindNextFile(hFind, &ffd) != 0)
			{
				if (StrCmpW(ffd.cFileName, L".") != 0 && StrCmpW(ffd.cFileName,L"..") != 0)
				{
					if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
					{
						list<wstring> innerFiles;
						MakeListInDir(*iter, innerFiles);

						count++;
						count = CalcTotalFiles(parent, innerFiles, count);
					}
					else
					{
						count++;
					}
				}
			}

			dwError = GetLastError();
			if (dwError != ERROR_NO_MORE_FILES) 
			{
				wcout << L"Unexpected Error." << endl;
				return -1;
			}

			FindClose(hFind);
		}

		return count;
	}

	static void GetImageSize(HPDF_REAL* width, HPDF_REAL* height, const wstring& imgFile);
	static void GetImageSizeInRect(HPDF_REAL* width, HPDF_REAL* height, HPDF_REAL rectW, HPDF_REAL rectH, const wstring& imgFile);
	//static void OptimizeImage(HPDF_REAL curW, HPDF_REAL curH, const wstring& imgFile);

	//static void FilesRefactorUtils::FillResponse(FRRESPONSE& res, FRRESPONSE_ENUM kind, wstring& text = L"", int sucFiles = 0, failFiles = 0);
	static void FilesRefactorUtils::FillResponse(FRRESPONSE& res, FRRESPONSE_ENUM kind, ACTION_TYPE type, 
		LPCWSTR text, int succeeded, int failed)
	{
		res.Kind = kind;
		res.Type = type;
		res.Text = text;
		res.NumOfFailed = failed;
		res.NumOfSucceeded = succeeded;
	}

	static bool FilesRefactorUtils::checkFileOpen(const wstring& fileName)
	{
		HANDLE hFile; 
		DWORD lastErr  = NO_ERROR;

		// Attempt to open the file exclusively.
		hFile = CreateFile(fileName.c_str(),                // name of the write
						   GENERIC_READ,          // open for writing
						   0,                      // do not share
						   NULL,                   // default security
						   OPEN_EXISTING,          // overwrite existing
						   FILE_ATTRIBUTE_NORMAL,  // normal file
						   NULL);                  // no attr. template

		if (hFile == INVALID_HANDLE_VALUE)
		{
			// Save last error...
			lastErr = GetLastError();
		}
		else
		{
			// If the open was successful, close the file.
			CloseHandle(hFile);
		}

		// Return TRUE if there is a sharing-violation.
		return ((hFile == INVALID_HANDLE_VALUE) && (lastErr == ERROR_SHARING_VIOLATION));
	}

	static bool ExistsInList(const wstring& val, const list<wstring>& li)
	{
		for (list<wstring>::const_iterator iter=li.begin(); iter!=li.end(); iter++)
		{
			if (*iter == val)
			{
				return true;
			}
		}

		return false;
	}

	static void getCopyOfName(const wstring& folder, const wstring& fileName, wstring& retVal)
	{
		list<wstring> li;
		MakeListInDir(folder, li);
		
		int count = 1;
		WCHAR str[10];
		_itow_s(count, str, 10, 10);
		wstring toCheck(folder + L"\\" + fileName + L"(" + wstring(str) + L")");
		while (true)
		{
			if (!ExistsInList(toCheck, li))
			{
				FileMachine retfm(NULL, wstring(toCheck));
				retfm.NamePlusExt(retVal);
				return;
			}

			count++;
			WCHAR str[10];
			_itow_s(count, str, 10, 10);
			toCheck = folder + L"\\" + fileName + L"(" + wstring(str) + L")";
		}
	}

	static wstring ToLower(const wstring& str)
	{
		wstring retVal = str;
		std::transform(retVal.begin(), retVal.end(), retVal.begin(), (int(*)(int)) tolower);
		return retVal;
	}

	static int GetEncoderCLSID(const WCHAR* format, CLSID* pClsid);

	static DWORD Touch(LPCTSTR lpszFile, FILETIME* atime, FILETIME* mtime, FILETIME* ctime);
};